fix(editor): reliable unsaved-changes tracking and save feedback - #125
Conversation
|
Warning Your free Security trial is over. An organization admin can activate billing to continue. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour. 📝 WalkthroughWalkthroughThe editor now defers initial checksum calculation. Document actions now detect head-matching content, reset local save state, show success notifications for explicit revision reuse, and prevent stale dirty indicators. ChangesSave state synchronization
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The PR makes localized editor state and save-feedback fixes; no actionable merge-blocking risk remains, so it is merge-ready after normal checks and review. Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Summary
Fixes the unreliable unsaved-changes tracking in the editor. Users saw four symptoms,
all stemming from the same state machine:
indicator — only the second did;
stayed in "Save" mode but the save path refused to run, so it neither saved nor closed;
indicator, and the save confirmation toast appeared only sometimes.
Related Issues
None.
Type of Change
Bug fix.
Changes
Four root causes, four targeted changes across two files:
packages/editor/src/Editor.tsx— the editor announces its initial contentchecksum via a window event dispatched in its mount effect. Listeners mounted in the
same React commit attach their effects after the child's, so the announcement was
lost and the first keystroke's checksum was mistaken for the loaded baseline (wiping
that keystroke's edit flag — the off-by-one). The dispatch is now deferred by one
tick so every listener from the same commit is attached first.
apps/web/src/components/documents/useDocumentActions.ts— dirty was a one-waylatch (
hasUserEdit): any keystroke set it, only a save cleared it, and nothingcompared content. Meanwhile the save path used a checksum comparison (
isUpToDate).The two could disagree — indicator on, save refusing — which is the unresponsive
button. The tab dirty flag is now
hasUserEdit && !isUpToDate: the indicator and thesave button consume the same condition and cannot deadlock. Undo back to the original
clears the indicator; redo re-lights it.
useDocumentActionsruns as three independentinstances per tab (tab button, Cmd+S in
edit-document.tsx, tab context menu), eachwith a private baseline; a save only reset the baseline of the instance that performed
it, so the other two kept pushing a stale opinion of "dirty" into the shared tab state
(last writer wins — the post-save flakiness). A render-phase adjustment now
re-baselines any instance whenever the current content is confirmed as the server's
current head revision (
isCurrentHeadContent, derived from the shared query caches),so all instances converge after any save. Implemented as render-phase state
adjustment, matching the file's existing baseline-capture pattern and the repo's
react-hooks/set-state-in-effectlint rule.already has,
handleUpdatetakes a head-move shortcut that produced no feedback atall ("the save card doesn't always come up"). Explicit saves on that path now show
the same "Document saved" toast; autosaves remain silent by design.
How to Test
Manual (no test harness in the repo yet). Run the app, open a document for editing,
and hard-refresh once before starting:
keystroke).
it returns.
"Document saved" confirmation appears every time, including when the content
matches an existing revision (was: sometimes silent).
(that deletion differs from what was just saved) and saving works.
Expected result: the unsaved indicator always reflects whether the content
actually differs from the last saved state, the save button and the indicator never
contradict each other, and every explicit save gives feedback.
Summary by CodeRabbit